package com.laf.common.utils;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.aliyun.ocr_api20210707.Client;
import com.aliyun.ocr_api20210707.models.RecognizeIdcardRequest;
import com.aliyun.ocr_api20210707.models.RecognizeIdcardResponse;
import com.aliyun.ocr_api20210707.models.RecognizeIdcardResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.laf.common.core.domain.OCRResult;
import com.laf.common.exception.ServiceException;
import com.laf.common.properties.OCRProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j
@Component
@RequiredArgsConstructor
public class IdOCRUtils {

    private final OCRProperties aliProperties;

    /**
     * ocr 识别
     */
    private Client client;
    /**
     * 线程池 （固定）
     */
    ExecutorService executorService = null;

    /**
     * 人脸识别
     */
    private final FaceUtils faceUtils;

    /**
     * 初始化 ocr 识别
     */
    @PostConstruct
    private void init() {
        Config config = new Config()
                .setAccessKeyId(aliProperties.getAccessKey())
                .setAccessKeySecret(aliProperties.getSecretKey());
        config.endpoint = aliProperties.getEndpointOcr();
        executorService = Executors.newFixedThreadPool(2);
        try {
            client = new Client(config);
            log.info("阿里云身份证识别初始化成功");
        } catch (Exception e) {
            log.error("阿里云身份证识别--初始化失败: {}", e.getMessage(), e);
            throw new RuntimeException("阿里云身份证识别初始化失败", e);
        }
    }

    /**
     * 识别身份证
     *
     * @param fileBack  背面 -> 国徽
     * @param fileFront 正面 -> 人脸
     * @return
     */
    public OCRResult identityCardUtilsOCR(MultipartFile fileBack, MultipartFile fileFront) throws Exception {
        OCRResult ocrResult = new OCRResult();

        // 执行身份证识别 （背面）
        Future<Boolean> futureBack = executorService.submit(() -> orcFileBack(fileBack, ocrResult));
        // 执行身份证识别 （正面）
        Future<?> futureFront = executorService.submit(() -> orcFileFront(fileFront, ocrResult));

        // 等待执行完成
        try {
            Boolean isExpired = futureBack.get(); // 获取背面ocr 结果
            if (isExpired) throw new ServiceException("身份证已过期");
            futureFront.get(); // 获取正面ocr 执行结果
            return ocrResult;
        } catch (InterruptedException | ExecutionException | ServiceException e) {
            log.error("身份证识别失败: {}", e.getMessage());
            throw new ServiceException("身份证识别失败");
        }
    }


    /**
     * 识别身份证背面带国徽的那一面
     *
     * @param file
     * @return
     */
    private boolean orcFileBack(MultipartFile file,
                                OCRResult ocrResult) {
        RecognizeIdcardRequest request = new RecognizeIdcardRequest();
        try {
            request.body = file.getInputStream();
        } catch (IOException e) {
            log.error("读取文件失败");
            throw new ServiceException("参数有误");
        }

        try {

            // 发送请求获取身份证的有效期范围
            RecognizeIdcardResponse response = client.recognizeIdcard(request);
            RecognizeIdcardResponseBody body = response.getBody();
            String str = JsonPath.read(body.getData(), "$.data.back.data.validPeriod");
            String[] split = str.split("-");
            if (split.length < 2) throw new ServiceException("身份证识别失败");

            // 检测身份证是否过期
            LocalDate localDate = LocalDateTimeUtil.parseDate(split[1], "yyyy.MM.dd");

            // 设置身份过期时间
            ocrResult.setExpiryDate(localDate);

            // 判断是是否过期了
            return localDate.isBefore(LocalDate.now());
        } catch (Exception e) {
            log.error("身份证识别失败 {} ", e.getMessage());
            throw new ServiceException("身份证识别失败");
        }
    }


    /**
     * 识别身份证 正面带人脸的那一面
     *
     * @param file
     * @param ocrResult
     * @return
     */
    private void orcFileFront(MultipartFile file,
                              OCRResult ocrResult) {
        RecognizeIdcardRequest request = new RecognizeIdcardRequest();

        try {
            request.body = file.getInputStream();
        } catch (IOException e) {
            log.error("读取文件失败");
            throw new ServiceException("参数有误");
        }
        try {
            request.setOutputFigure(true);
            // 发送请求获取身份证信息
            RecognizeIdcardResponse response = client.recognizeIdcard(request);
            RecognizeIdcardResponseBody body = response.getBody();
            System.out.println("data = " + body.getData());
            // 解析返回结果并且设置放回值
            DocumentContext documentContext = JsonPath.parse(body.data);
            OCRResult info = documentContext.read("$.data.face.data", OCRResult.class);
            ocrResult.setName(info.getName());
            ocrResult.setSex(info.getSex());
            ocrResult.setBirthDate(info.getBirthDate());
            ocrResult.setEthnicity(info.getEthnicity());
            ocrResult.setAddress(info.getAddress());
            ocrResult.setIdNumber(info.getIdNumber());

            // 获取特征码（特性信息是 byte [] 转换成 base64  ）
            String faceCode = getFaceCode(file, documentContext);
            ocrResult.setBase64(faceCode);


        } catch (Exception e) {
            log.error("读取身份结果失败：{}", e.getMessage());
            throw new ServiceException("身份证识别失败");
        }
    }

    /**
     * @param file            身份证带人脸脸的那一面
     * @param documentContext json 数据解析对象
     * @return base64 编码
     * @throws IOException
     */
    private String getFaceCode(MultipartFile file, DocumentContext documentContext) throws IOException {
        // 获取人脸坐标，并且截取人脸图片，根据人脸图片获取人脸特征

        // 1. 获取人脸图片位置和大小
        List faceListPoint = documentContext.read("$.data.face.figure", List.class);
        if (faceListPoint.size() == 0) throw new ServiceException("身份证识别失败");
        Integer x = documentContext.read("$.data.face.figure[0].x", Integer.class);
        Integer y = documentContext.read("$.data.face.figure[0].y", Integer.class);
        Integer w = documentContext.read("$.data.face.figure[0].w", Integer.class);
        Integer h = documentContext.read("$.data.face.figure[0].h", Integer.class);

        // 2. 将人脸图片中身份证中截取出来
        BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
        BufferedImage faceBufferedImage = bufferedImage.getSubimage(x, y, w, h);

        // 3. 将人脸图片转换 byte[] 数组
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(faceBufferedImage, "png", baos);
        byte[] byteArray = baos.toByteArray();
        baos.close();

        // 4. 获取人脸的base64 编码
        ImageInfo data = ImageFactory.getRGBData(byteArray);
        return faceUtils.getFaceCode(data);
    }

}
